/*
    SEL - Simple DirectMedia Layer Extension Library
    Copyright (C) 2001 Matej Knopp <knopp@users.sf.net>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation; either
    version 2 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public
    License along with this library; if not, write to the Free
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include "surface.h"
#include "error.h"
#include "debug.h"

namespace SEL
{

void
Surface::releaseSurface ()
{
	if (m_auto_free && m_surface)
        	SDL::FreeSurface (m_surface);
	if (m_parent)
        	Object::unref (m_parent);
                m_parent = NULL;
}

void
Surface::wrapSurface (SDL::Surface *wrap_me, bool auto_free)
{
	if (!wrap_me) {
		debug ("Tried to wrap (null) surface!");
        	throw Error (SDL::GetError ());
        }
	m_auto_free = auto_free;
        m_surface = wrap_me;
        m_parent = NULL;
}

Surface *
Surface::createChildSurface (Surface *src, const SDL::Rect &rect)
{
        if (!src)
        	throw Error ("Couldn't create a child of (null) surface.");

        Uint8 *addr = ((Uint8 *)src->pixels()) +
			rect.x * src->format()->BytesPerPixel +
                        rect.y * src->pitch();

	Surface *surface =
	        new Surface (addr, rect.w, rect.h,
                	     src->format ()->BitsPerPixel,
                             src->pitch (),
                             src->format ()->Rmask,
                             src->format ()->Gmask,
                             src->format ()->Bmask,
                             src->format ()->Amask);
	surface->m_parent = src;
        src->ref ();
        return surface;
}

// grr.. how can I be so lazy...
// these two are taken from the SDL and _very_ little modified...

/*
 * Free a previously allocated format structure
 */
static void freeFormat(SDL::PixelFormat *format)
{
	if ( format ) {
		if ( format->palette ) {
			if ( format->palette->colors ) {
				free(format->palette->colors);
			}
			free(format->palette);
		}
		free(format);
	}
}

/*
 * Allocate a pixel format structure and fill it according to the given info.
 */
static SDL::PixelFormat *allocFormat(int bpp, Uint32 Rmask,  Uint32 Gmask,
				     Uint32 Bmask, Uint32 Amask)
{
	SDL::PixelFormat *format;
	Uint32 mask;

	/* Allocate an empty pixel format structure */
	format = (SDL::PixelFormat*)malloc(sizeof(*format));
	if ( format == NULL )
	{
		throw Error ("Out of memory.");
		return(NULL);
	}
	memset(format, 0, sizeof(*format));
	format->alpha = SDL::ALPHA_OPAQUE;

	/* Set up the format */
	format->BitsPerPixel = bpp;
	format->BytesPerPixel = (bpp+7)/8;
	switch (bpp)
	{
		case 1:
			/* Create the 2 color black-white palette */
			format->palette = (SDL::Palette *)malloc(
							sizeof(SDL::Palette));
			if ( format->palette == NULL )
			{
				freeFormat(format);
				throw Error ("Out of memory.");
				return(NULL);
			}
			(format->palette)->ncolors = 2;
			(format->palette)->colors = (SDL::Color *)malloc(
				(format->palette)->ncolors*sizeof(SDL::Color));
			if ( (format->palette)->colors == NULL )
			{
				freeFormat(format);
				throw Error ("Out of memory.");
				return(NULL);
			}
			format->palette->colors[0].r = 0xFF;
			format->palette->colors[0].g = 0xFF;
			format->palette->colors[0].b = 0xFF;
			format->palette->colors[1].r = 0x00;
			format->palette->colors[1].g = 0x00;
			format->palette->colors[1].b = 0x00;
			format->Rloss = 8;
			format->Gloss = 8;
			format->Bloss = 8;
			format->Aloss = 8;
			format->Rshift = 0;
			format->Gshift = 0;
			format->Bshift = 0;
			format->Ashift = 0;
			format->Rmask = 0;
			format->Gmask = 0;
			format->Bmask = 0;
			format->Amask = 0;
			break;

		case 4:
			/* Create the 16 color VGA palette */
			format->palette = (SDL::Palette *)malloc(
							sizeof(SDL::Palette));
			if ( format->palette == NULL )
			{
				freeFormat(format);
				throw Error ("Out of memory.");
				return(NULL);
			}
			(format->palette)->ncolors = 16;
			(format->palette)->colors = (SDL::Color *)malloc(
				(format->palette)->ncolors*sizeof(SDL::Color));
			if ( (format->palette)->colors == NULL )
			{
				freeFormat (format);
				throw Error ("Out of memory.");
				return(NULL);
			}
			/* Punt for now, will this ever be used? */
			memset((format->palette)->colors, 0,
				(format->palette)->ncolors*sizeof(SDL::Color));

			/* Palettized formats have no mask info */
			format->Rloss = 8;
			format->Gloss = 8;
			format->Bloss = 8;
			format->Aloss = 8;
			format->Rshift = 0;
			format->Gshift = 0;
			format->Bshift = 0;
			format->Ashift = 0;
			format->Rmask = 0;
			format->Gmask = 0;
			format->Bmask = 0;
			format->Amask = 0;
			break;

		case 8:
			/* Create an empty 256 color palette */
			format->palette = (SDL::Palette *)malloc(
							sizeof(SDL::Palette));
			if ( format->palette == NULL )
			{
				freeFormat(format);
				throw Error ("Out of memory.");
				return(NULL);
			}
			(format->palette)->ncolors = 256;
			(format->palette)->colors = (SDL::Color *)malloc(
				(format->palette)->ncolors*sizeof(SDL::Color));
			if ( (format->palette)->colors == NULL )
			{
				freeFormat(format);
				throw Error ("Out of memory.");
				return(NULL);
			}
			memset((format->palette)->colors, 0,
				(format->palette)->ncolors*sizeof(SDL::Color));

			/* Palettized formats have no mask info */
			format->Rloss = 8;
			format->Gloss = 8;
			format->Bloss = 8;
			format->Aloss = 8;
			format->Rshift = 0;
			format->Gshift = 0;
			format->Bshift = 0;
			format->Ashift = 0;
			format->Rmask = 0;
			format->Gmask = 0;
			format->Bmask = 0;
			format->Amask = 0;
			break;

		default:
			/* No palette, just packed pixel info */
			format->palette = NULL;
			format->Rshift = 0;
			format->Rloss = 8;
			if ( Rmask ) {
				for ( mask = Rmask; !(mask&0x01); mask >>= 1 )
					++format->Rshift;
				for ( ; (mask&0x01); mask >>= 1 )
					--format->Rloss;
			}
			format->Gshift = 0;
			format->Gloss = 8;
			if ( Gmask ) {
				for ( mask = Gmask; !(mask&0x01); mask >>= 1 )
					++format->Gshift;
				for ( ; (mask&0x01); mask >>= 1 )
					--format->Gloss;
			}
			format->Bshift = 0;
			format->Bloss = 8;
			if ( Bmask )
			{
				for ( mask = Bmask; !(mask&0x01); mask >>= 1 )
					++format->Bshift;
				for ( ; (mask&0x01); mask >>= 1 )
					--format->Bloss;
			}
			format->Ashift = 0;
			format->Aloss = 8;
			if ( Amask ) {
				for ( mask = Amask; !(mask&0x01); mask >>= 1 )
					++format->Ashift;
				for ( ; (mask&0x01); mask >>= 1 )
					--format->Aloss;
			}
			format->Rmask = Rmask;
			format->Gmask = Gmask;
			format->Bmask = Bmask;
			format->Amask = Amask;
			break;
	}
	/* Calculate some standard bitmasks, if necessary
	 * Note:  This could conflict with an alpha mask, if given.
	 */
	if ( (bpp > 8) && !format->Rmask && !format->Gmask && !format->Bmask )
	{
		/* R-G-B */
		if ( bpp > 24 )
			bpp = 24;
		format->Rloss = 8-(bpp/3);
		format->Gloss = 8-(bpp/3)-(bpp%3);
		format->Bloss = 8-(bpp/3);
		format->Rshift = ((bpp/3)+(bpp%3))+(bpp/3);
		format->Gshift = (bpp/3);
		format->Bshift = 0;
		format->Rmask = ((0xFF>>format->Rloss)<<format->Rshift);
		format->Gmask = ((0xFF>>format->Gloss)<<format->Gshift);
		format->Bmask = ((0xFF>>format->Bloss)<<format->Bshift);
	}
	return(format);
}

static SDL::Surface *
ConvertSurfaceAlpha (const SDL::PixelFormat &fmt, SDL::Surface *surface)
{
	/* default to ARGB8888 */
	Uint32 amask = 0xff000000;
	Uint32 rmask = 0x00ff0000;
	Uint32 gmask = 0x0000ff00;
	Uint32 bmask = 0x000000ff;

	return_val_if_fail (surface != NULL, NULL);

	switch(fmt.BytesPerPixel) {
	    case 2:
		/* For XGY5[56]5, use, AXGY8888, where {X, Y} = {R, B}.
		   For anything else (like ARGB4444) it doesn't matter
		   since we have no special code for it anyway */
		if ( (fmt.Rmask == 0x1f) &&
		     (fmt.Bmask == 0xf800 || fmt.Bmask == 0x7c00)) {
			rmask = 0xff;
			bmask = 0xff0000;
		}
		break;

	    case 3:
	    case 4:
		/* Keep the video format, as long as the high 8 bits are
		   unused or alpha */
		if ( (fmt.Rmask == 0xff) && (fmt.Bmask == 0xff0000) ) {
			rmask = 0xff;
			bmask = 0xff0000;
		}
		break;

	    default:
		/* We have no other optimised formats right now. When/if a new
		   optimised alpha format is written, add the converter here */
		break;
	}

	SDL::PixelFormat *_format;
	Uint32 _flags;

	_format = allocFormat(32, rmask, gmask, bmask, amask);
	_flags = surface->flags & SDL::HWSURFACE;
	_flags |= surface->flags & (SDL::SRCALPHA | SDL::RLEACCELOK);
	SDL::Surface *new_surface = SDL::ConvertSurface(surface, _format, _flags);
	freeFormat(_format);
	return new_surface;
}

Surface *
Surface::convertSurfaceAlpha (SDL::Surface *src,
			      const SDL::PixelFormat &fmt)
{
	SDL::Surface *surface = ConvertSurfaceAlpha (fmt, src);
	return new Surface (surface);
}

Surface *
Surface::convertSurfaceAlpha (Surface *src,
			      const SDL::PixelFormat &fmt)
{
	SDL::Surface *surface = ConvertSurfaceAlpha (fmt, src->surface ());
	return new Surface (surface);
}

void
Surface::convertSurfaceAlpha (const SDL::PixelFormat &fmt)
{
	SDL::Surface *surface = ConvertSurfaceAlpha (fmt, m_surface);
	releaseSurface ();
	wrapSurface (surface);
}

Display::Display (int w, int h, int bpp, Uint32 flags)
{
	SDL::Surface *surface = SDL::SetVideoMode (w, h, bpp, flags);
	VideoInfo::getVideoInfo (); // Update video information
        wrapSurface (surface);
}

void
Display::setVideoMode (int w, int h, int bpp, Uint32 flags)
{
	// ??? Should I destroy the last surface ?
	SDL::Surface *surface = SDL::SetVideoMode (w, h, bpp, flags);
	VideoInfo::getVideoInfo (); // Update video information
        wrapSurface (surface);
}

} // namespace SEL
